﻿using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Collections;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation.StringValues;
using Xms.Abp.Data;
using Xms.Abp.Domain;
using Xms.Abp.Settings;
using Xms.Abp.Validation.StringValues;
using ISettingEncryptionService = Volo.Abp.Settings.ISettingEncryptionService;

namespace SettingsManagement;

public class SettingsManager : XmsDomainService, ISettingsManager, ISingletonDependency
{
    protected ISettingsDefinitionManager SettingsDefinitionManager => LazyInject<ISettingsDefinitionManager>();
    protected ISettingEncryptionService SettingEncryptionService => LazyInject<ISettingEncryptionService>();
    protected ISettingsStore SettingsStore => LazyInject<ISettingsStore>();
    protected List<ISettingsValueProvider> Providers => _lazyProviders.Value;
    protected SettingsOptions Options { get; }

    private readonly Lazy<List<ISettingsValueProvider>> _lazyProviders;

    protected IStringLocalizerFactory StringLocalizerFactory => LazyInject<IStringLocalizerFactory>();

    public SettingsManager(
        IOptions<SettingsOptions> options,
        IServiceProvider serviceProvider)
    {
        Options = options.Value;

        //TODO: Instead, use IHybridServiceScopeFactory and create a scope..?

        _lazyProviders = new Lazy<List<ISettingsValueProvider>>(
            () => Options
                .ValueProviders
                .Select(c => serviceProvider.GetRequiredService(c) as ISettingsValueProvider)
                .ToList(),
            true
        );
    }


    protected async Task<SettingsValues> GetOrNullAsync(SettingsTypeDefinition typeDefinition, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var providers = Providers.Reverse<ISettingsValueProvider>().ToArray();
        var index = Array.FindIndex(providers, p => p.Name == providerName);
        providers = providers.Skip(index).ToArray();
        var values = new SettingsValues();
        if (!typeDefinition.IsInherited)
        {
            if (providerName != typeDefinition.ProviderName)
            {
                return null;
            }
            var provider = providers.FirstOrDefault(r => r.Name == typeDefinition.ProviderName);
            values.Values = await provider?.GetValuesAsync(typeDefinition.TypeName, providerKey, cancellationToken);
            foreach (var property in typeDefinition.Settings)
            {
                if (property.ValueType == DataType.Dictionary)
                {
                    var vs = await provider?.GetValuesAsync(typeDefinition.TypeName + "." + property.Name, providerKey, cancellationToken);
                    values.Dictionaries[property.Name] = vs;
                }
            }
            return values;
        }
        ISettingsValueProvider prd = null;
        foreach (var provider in providers)
        {
            values.Values = await provider?.GetValuesAsync(typeDefinition.TypeName, providerKey, cancellationToken);
            if (values.Values != null && values.Values.Count > 0)
            {
                prd = provider;
                break;
            }
        }
        if (prd != null)
        {
            foreach (var property in typeDefinition.Settings)
            {
                if (property.ValueType == DataType.Dictionary)
                {
                    var vs = await prd.GetValuesAsync(typeDefinition.TypeName + "." + property.Name, providerKey, cancellationToken);
                    values.Dictionaries[property.Name] = vs;
                }
            }
        }
        return values;
    }
    public async Task<SettingsValues> GetOrNullAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var typeDefinition = SettingsDefinitionManager.GetType(typeName);
        return await GetOrNullAsync(typeDefinition, providerName, providerName, cancellationToken);
    }

    public async Task<List<SettingValue>> GetValuesAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var typeDefinition = SettingsDefinitionManager.GetType(typeName);
        var providers = Providers.Reverse<ISettingsValueProvider>().ToArray();
        var index = Array.FindIndex(providers, p => p.Name == providerName);
        providers = providers.Skip(index).ToArray();
        var values = new List<SettingValue>();
        if (!typeDefinition.IsInherited)
        {
            if (providerName != typeDefinition.ProviderName)
            {
                return null;
            }
            var provider = providers.FirstOrDefault(r => r.Name == typeDefinition.ProviderName);
            values = await provider?.GetValuesAsync(typeDefinition.TypeName, providerKey, cancellationToken);
            return values;
        }
        foreach (var provider in providers)
        {
            values = await provider?.GetValuesAsync(typeDefinition.TypeName, providerKey, cancellationToken);
            if (values != null && values.Count > 0)
            {
                break;
            }
        }
        return values;
    }

    public async Task<List<SettingValue>> GetDictionaryValuesAsync(string typeName, string name, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var typeDefinition = SettingsDefinitionManager.GetType(typeName);
        if (!typeDefinition.Settings.Any(r => r.Name == name))
        {
            return null;
        }
        var providers = Providers.Reverse<ISettingsValueProvider>().ToArray();
        var index = Array.FindIndex(providers, p => p.Name == providerName);
        providers = providers.Skip(index).ToArray();
        var tName = typeDefinition.TypeName + "." + name;
        if (!typeDefinition.IsInherited)
        {
            if (providerName != typeDefinition.ProviderName)
            {
                return null;
            }
            var provider = providers.FirstOrDefault(r => r.Name == typeDefinition.ProviderName);
            return await provider?.GetValuesAsync(tName, providerKey, cancellationToken);
        }
        foreach (var provider in providers)
        {
            var values = await provider?.GetValuesAsync(tName, providerKey, cancellationToken);
            if (values != null && values.Count > 0)
            {
                return values;
            }
        }
        return null;
    }

    protected SettingsInfo GetSettingsInfo(SettingsTypeDefinition typeDefinition, string providerName, SettingsValues values = null, bool loadProperties = false)
    {
        var info = new SettingsInfo(typeDefinition.TypeName, providerName)
        {
            IsEditable = typeDefinition.IsEditable,
            DisplayName = typeDefinition.DisplayName.Localize(StringLocalizerFactory),
            Description = typeDefinition.Description.Localize(StringLocalizerFactory),
        };
        if (loadProperties)
        {
            var dict = values.Values.ToDictionary(r => r.Name, r => r.Value);
            foreach (var property in typeDefinition.Settings)
            {
                var propertyInfo = new SettingsPropertyInfo
                {
                    Name = property.Name,
                    DisplayName = property.DisplayName.Localize(StringLocalizerFactory),
                    Description = property.Description.Localize(StringLocalizerFactory),
                    ValueType = property.ValueType,
                };
                if (property.ValueType != DataType.Dictionary)
                {
                    propertyInfo.Value = dict[property.Name];
                }
                else
                {
                    propertyInfo.DictionaryValue = values.Dictionaries[property.Name];
                }
                info.Properties.Add(propertyInfo);
            }
        }
        return info;
    }
    public async Task<SettingsInfo> GetSettingsInfoAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var typeDefinition = SettingsDefinitionManager.GetType(typeName);
        var values = await GetOrNullAsync(typeDefinition, providerName, providerKey, cancellationToken);
        return GetSettingsInfo(typeDefinition, providerName, values, true);
    }

    public async Task SetAsync(string typeName, SettingsValues values, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        Check.NotNull(values, nameof(values));
        Check.NotNull(values.Values, "values.Values");

        var typeDefinition = SettingsDefinitionManager.GetType(typeName);
        var provider = Providers.FirstOrDefault(r => r.Name == providerName);
        if (provider != null)
        {
            await provider.SetValuesAsync(typeName, providerKey, values.Values, cancellationToken);

            foreach (var property in typeDefinition.Settings)
            {
                if (property.ValueType == DataType.Dictionary)
                {
                    if (values.Dictionaries != null && values.Dictionaries.ContainsKey(property.Name))
                    {
                        await provider.SetValuesAsync(typeName + "." + property.Name, providerKey, values.Dictionaries[property.Name], cancellationToken);
                    }
                    else
                    {
                        await provider.ResetValuesAsync(typeName + "." + property.Name, providerKey, cancellationToken);
                    }
                }
            }
        }
    }

    public async Task ResetAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var provider = Providers.FirstOrDefault(r => r.Name == providerName);
        await provider?.ResetValuesAsync(typeName, providerKey, cancellationToken);
    }

    private List<SettingsTypeInfo> GetSettingsTypes(IEnumerable<SettingsTypeDefinition> typeDefinitions, string providerName)
    {
        var list = new List<SettingsTypeInfo>();
        foreach (var typeDefinition in typeDefinitions)
        {
            list.Add(GetSettingsInfo(typeDefinition, providerName));
        }
        return list;
    }

    public List<SettingsTypeInfo> GetEditableSettingsTypes(string providerName)
    {
        return GetSettingsTypes(SettingsDefinitionManager.GetTypes().Where(r => r.SettingsType == SettingsType.Normal), providerName);
    }

    public List<SettingsTypeInfo> GetEditableEnumSettingsTypes(string providerName)
    {
        return GetSettingsTypes(SettingsDefinitionManager.GetTypes().Where(r => r.SettingsType == SettingsType.Enum), providerName);
    }
}
